﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;

namespace Dapper.Library
{
    public sealed class WhereExpression : ExpressionVisitor
    {
        #region sql指令

        private readonly StringBuilder _sqlCmd;

        /// <summary>
        /// sql指令
        /// </summary>
        public string SqlCmd => _sqlCmd.Length > 0 ? $" WHERE {_sqlCmd} " : "";

        public DynamicParameters Param { get; }

        private string _tempFieldName;

        private string TempFieldName
        {
            get => _prefix + _tempFieldName;
            set => _tempFieldName = value;
        }

        private string ParamName => _parameterPrefix + TempFieldName;

        private readonly string _prefix;

        private readonly char _parameterPrefix;

        private readonly char _closeQuote;

        private readonly char _openQuote;

        private static readonly List<string> TypeList = new List<string> { "=", "!=", "<", "<=", ">", ">=" };
        #endregion

        #region 执行解析

        /// <inheritdoc />
        /// <summary>
        /// 执行解析
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="prefix">字段前缀</param>
        /// <param name="symbolInfo"></param>
        /// <returns></returns>
        public WhereExpression(LambdaExpression expression, string prefix, SymbolInfo symbolInfo)
        {
            _sqlCmd = new StringBuilder(100);
            Param = new DynamicParameters();

            _prefix = prefix;
            _parameterPrefix = symbolInfo.ParameterPrefix;
            _openQuote = symbolInfo.OpenQuote;
            _closeQuote = symbolInfo.CloseQuote;
            if (expression!=null&&expression.ToString().Contains("Nullable"))
            {
                Visit(expression);
            }
            else
            {
                var exp = TrimExpression.Trim(expression);
                Visit(exp);
            }
        }
        #endregion

        #region 访问成员表达式

        /// <inheritdoc />
        /// <summary>
        /// 访问成员表达式
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitMember(MemberExpression node)
        {

            var typeInfo = TypeList.Find(s => _sqlCmd.ToString().TrimEnd().EndsWith(s));
            if (string.IsNullOrEmpty(typeInfo))
            {
                _sqlCmd.Append(_openQuote + node.Member.GetColumnAttributeName() + _closeQuote);
                TempFieldName = node.Member.Name;
            }
            else
            {
                TempFieldName = node.Member.Name;
                _sqlCmd.AppendFormat(ParamName);

                //下面是声明变量
                var argumentExpression = node.ToConvertAndGetValue();
                Param.Add(TempFieldName, argumentExpression);
            }

            return node;
        }




        #endregion

        #region 访问二元表达式
        /// <inheritdoc />
        /// <summary>
        /// 访问二元表达式
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitBinary(BinaryExpression node)
        {
            _sqlCmd.Append("(");
            Visit(node.Left);

            _sqlCmd.Append(node.GetExpressionType());

            Visit(node.Right);

            _sqlCmd.Append(")");

            return node;

        }
        #endregion

        #region 访问常量表达式
        /// <inheritdoc />
        /// <summary>
        /// 访问常量表达式
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitConstant(ConstantExpression node)
        {
            SetParam(node.Value);

            return node;
        }
        #endregion

        #region 访问方法表达式
        /// <inheritdoc />
        /// <summary>
        /// 访问方法表达式
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            //typeof(Animal).IsAssignFrom(typeof(Dog))  他返回true的条件是 Dog类直接或间接的实现了Animal类；继承也可以

            if (node.Method.DeclaringType.FullName.Contains("Convert"))//使用convert函数里待执行的sql数据
            {
                Visit(node.Arguments[0]);
            }
            else if (node.Method.Name == "Contains" && typeof(IEnumerable).IsAssignableFrom(node.Method.DeclaringType) &&
                node.Method.DeclaringType != typeof(string))
                In(node);
            else if (node.Method.Name == "Equals")
                Equal(node);
            else if (node.Method.DeclaringType.FullName.Equals("Dapper.Library.ExpressExpansion"))//自定义扩展方法
            {
                Visit(node.Arguments[0]);
                Operation(node);
            }
            else
                Like(node);

            return node;
        }

        #endregion

        private void SetParam(object value)
        {
            if (value != null)
            {
                _sqlCmd.Append(ParamName);
                Param.Add(TempFieldName, value);
            }
            else
            {
                _sqlCmd.Append("NULL");
            }
        }

        private void Like(MethodCallExpression node)
        {
            Visit(node.Object);
            _sqlCmd.AppendFormat(" LIKE {0}", ParamName);
            switch (node.Method.Name)
            {
                case "StartsWith":
                    {
                        //var argumentExpression = (ConstantExpression)node.Arguments[0];
                        Param.Add(TempFieldName, node.Arguments[0].ToConvertAndGetValue() + "%");
                    }
                    break;
                case "EndsWith":
                    {
                        //var argumentExpression = (ConstantExpression)node.Arguments[0];
                        //Param.Add(TempFieldName, "%" + argumentExpression.Value);
                        Param.Add(TempFieldName, "%" + node.Arguments[0].ToConvertAndGetValue());
                    }
                    break;
                case "Contains":
                    {
                        //var argumentExpression = (ConstantExpression)node.Arguments[0];
                        Param.Add(TempFieldName, "%" + node.Arguments[0].ToConvertAndGetValue() + "%");
                    }
                    break;
                default:
                    throw new Exception("表达式不支持此函数");
            }
        }

        private void Equal(MethodCallExpression node)
        {
            Visit(node.Object);
            _sqlCmd.AppendFormat(" ={0}", ParamName);
            var argumentExpression = node.Arguments[0].ToConvertAndGetValue();
            Param.Add(TempFieldName, argumentExpression);
        }

        private void In(MethodCallExpression node)
        {
            var arrayValue = (IList)((ConstantExpression)node.Object).Value;
            if (arrayValue.Count == 0)
            {
                _sqlCmd.Append(" 1 = 2");
                return;
            }
            Visit(node.Arguments[0]);
            _sqlCmd.AppendFormat(" IN {0}", ParamName);
            Param.Add(TempFieldName, arrayValue);
        }

        private void Operation(MethodCallExpression node)
        {
            switch (node.Method.Name)
            {
                case "WhereIn":
                    {
                        _sqlCmd.Append($" IN {ParamName}");
                        object value = node.Arguments[1].ToConvertAndGetValue();
                        Param.Add(ParamName, value);
                    }
                    break;
                case "WhereNotIn":
                    {
                        _sqlCmd.Append($" NOT IN {ParamName}");
                        object value = node.Arguments[1].ToConvertAndGetValue();
                        Param.Add(ParamName, value);
                    }
                    break;
                case "WhereIsNull":
                    {
                        _sqlCmd.Append(" IS NULL");
                    }
                    break;
                case "WhereIsNotNull":
                    {
                        _sqlCmd.Append(" IS NOT NULL");
                    }
                    break;
                case "WhereBetween":
                    {
                        string fromParam = ParamName + "_From";
                        string toParam = ParamName + "_To";
                        _sqlCmd.Append($" BETWEEN {fromParam} AND {toParam}");
                        Param.Add(fromParam, node.Arguments[1].ToConvertAndGetValue());
                        Param.Add(toParam, node.Arguments[2].ToConvertAndGetValue());
                    }
                    break;
                #region Convert转换计算
                case "ToInt32":
                case "ToString":
                case "ToDecimal":
                case "ToDouble":
                    {
                        Visit(node.Object);
                        if (node.Arguments.Count > 0)//Convert.ToInt32("xxx") or Convert.ToString("xxx")
                        {
                            _sqlCmd.AppendFormat(" {0}", ParamName);
                            var argumentExpression = (ConstantExpression)node.Arguments[0];
                            Param.Add(ParamName, argumentExpression.ToConvertAndGetValue());
                        }
                        else
                        {
                            //xxx.ToString
                        }
                    }
                    break;
                #endregion
                #region 时间计算
                case "AddYears":
                case "AddMonths":
                case "AddDays":
                case "AddHours":
                case "AddMinutes":
                case "AddSeconds":
                    {
                        if (!(node.Object is MemberExpression))//数值比对（DateTime.Now.AddDays）
                        {
                            object time = node.ToConvertAndGetValue();
                            _sqlCmd.AppendFormat(" {0}", ParamName);
                            Param.Add(ParamName, time);
                        }
                        else//字段比对(类似CreateDate.AddDays)
                        {
                            //var member = (MemberExpression)(node.Object);
                            //var entity = EntityCache.QueryEntity(member.Member.DeclaringType);
                            //var dateOption = (DateOption)Enum.Parse(typeof(DateOption), node.Method.Name);
                            //string fieldName = entity.FieldPairs[member.Member.Name];
                            //string sqlCombine = providerOption.CombineDate(dateOption, entity.AsName, fieldName, node.Arguments[0].ToConvertAndGetValue().ToString());
                            //this.SpliceField.Append(sqlCombine);
                            ////重新计算参数名称
                            //FieldName = entity.AsName + "_" + fieldName;
                        }
                    }
                    break;
                #endregion
                default:
                    throw new Exception("the expression is no support this function");
            }
        }
    }
}
